Skip to content

feat(prompt): add custom output format sequences#182

Merged
legeling merged 2 commits into
mainfrom
agent/prompt-output-format
Jul 9, 2026
Merged

feat(prompt): add custom output format sequences#182
legeling merged 2 commits into
mainfrom
agent/prompt-output-format

Conversation

@legeling

@legeling legeling commented Jul 9, 2026

Copy link
Copy Markdown
Owner

Summary

  • Ports the useful Prompt output-format contribution from jazzson51569/PromptHub@fa7b0e6b into a clean upstream branch.
  • Adds persistent Prompt output format sequences so one Prompt copy action can concatenate several Prompt bodies.
  • Adds desktop IPC/preload/store wiring, a detail-page configuration panel, seven-locale copy, and DB regression coverage.

Scope Notes

This intentionally does not merge the contributor fork branch directly. The fork branch contains unrelated build/debug/data-path/package changes, so this PR cherry-picks the product feature only.

Verification

  • pnpm typecheck
  • pnpm --filter @prompthub/desktop test -- tests/unit/main/prompt-output-format-db.test.ts tests/unit/components/prompt-detail-metadata.test.tsx --run

Co-authored-by: jazzson51569 jazzson51569@153.com

Summary by CodeRabbit

  • 新功能

    • 新增“自定义输出格式”设置面板,可添加、搜索、删除并拖拽调整输出顺序。
    • 复制 Prompt 时支持按配置好的输出顺序拼接内容,变量仍会按原流程逐步填写。
  • Bug 修复

    • 导入、导出和恢复备份时 теперь会完整保留输出格式相关数据,并正确统计跳过项。
    • 删除相关 Prompt 后,关联的输出格式项会自动清理。

@vercel

vercel Bot commented Jul 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
prompt-hub Ready Ready Preview, Comment Jul 9, 2026 11:12am

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

新增“Prompt 输出格式”功能:新数据库表 prompt_output_format_items 与 PromptOutputFormatDB,配套 IPC 通道/处理器/preload/渲染服务,Prompt store 状态与动作,新的 PromptOutputFormatPanel 面板与详情入口,复制流程支持多 Prompt 队列拼接,备份导入导出支持该数据,多语言文案与 spec 文档,及相应单元测试。

Changes

Prompt 输出格式功能

Layer / File(s) Summary
Schema 与数据库层
packages/db/src/schema.ts, packages/db/src/prompt-output-format.ts, packages/db/src/index.ts, packages/shared/types/prompt.ts, apps/desktop/tests/unit/main/prompt-output-format-db.test.ts
新增 prompt_output_format_items 表、索引与部分唯一约束;实现 PromptOutputFormatDB 的创建/更新/重排/查询/删除及排序归一化;定义 OutputFormatItem/DTO 类型;新增单元测试覆盖 self 唯一性、重排与级联清理。
IPC 通道与桌面接入
packages/shared/constants/ipc-channels.ts, apps/desktop/src/main/database/index.ts, apps/desktop/src/main/ipc/index.ts, apps/desktop/src/main/ipc/prompt.ipc.ts, apps/desktop/src/preload/api/prompt.ts, apps/desktop/src/renderer/services/database.ts
新增 PROMPT_OUTPUT_FORMAT_* IPC 通道常量;主进程注册对应 CRUD/重排处理器并触发 syncWorkspace;preload 暴露方法;渲染服务封装分流至 IPC。
Prompt store 与 UI 面板
apps/desktop/src/renderer/stores/prompt.store.ts, apps/desktop/src/renderer/components/prompt/PromptOutputFormatPanel.tsx, apps/desktop/src/renderer/components/prompt/PromptDetailMetadata.tsx
store 新增 outputFormatItems 状态与增删查重排动作;新增可拖拽/搜索的输出格式面板组件;详情元信息新增输出格式折叠按钮与计数。
MainContent 复制队列集成
apps/desktop/src/renderer/components/layout/MainContent.tsx
接入面板与 store,新增 getOutputFormatPromptQueue 与队列状态,重写 handleCopyPrompt 支持多 Prompt 队列拼接复制及变量弹窗逐步推进。
备份导入导出与预览
apps/desktop/src/renderer/services/database-backup-format.ts, apps/desktop/src/renderer/services/database-backup.ts, apps/desktop/src/renderer/hooks/useBackupImportController.tsx, apps/desktop/src/renderer/components/settings/BackupImportConfirmDialog.tsx, apps/desktop/tests/unit/services/database-backup*.test.ts, apps/desktop/tests/unit/components/data-settings.test.tsx
DatabaseBackup/ImportSkippedStats/ImportPreviewSummary 扩展 outputFormatItems;新增结构校验、孤儿引用清洗、跳过统计;导出/导入/选择性导出全链路贯通;新增/更新对应单元测试。
多语言与规范文档
apps/desktop/src/renderer/i18n/locales/*.json, spec/changes/active/prompt-output-format-contribution/*
8 种语言新增 customOutputFormat 文案;新增 design/proposal/tasks/spec 文档记录该特性移植说明。

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

  • legeling/PromptHub#173: 同样在 apps/desktop/src/main/ipc/prompt.ipc.tsregisterPromptIPC 中新增 IPC 处理器注册,涉及类似的处理器注册模式。

Poem

一只兔子敲敲键盘,
给 Prompt 排上队列站,
拖一拖,序号排排坐,
复制一下全接上~ 🐇✂️
输出格式,条条分明,
备份归档也不留空。

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.03% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题准确概括了本次变更的核心:为 Prompt 增加可配置的自定义输出格式序列。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agent/prompt-output-format

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Port the Prompt output format contribution from jazzson51569/PromptHub commit fa7b0e6 into a clean upstream branch.

Refs: jazzson51569/PromptHub@fa7b0e6b

Verification:\n- pnpm typecheck\n- pnpm --filter @prompthub/desktop test -- tests/unit/main/prompt-output-format-db.test.ts tests/unit/components/prompt-detail-metadata.test.tsx --run

Co-authored-by: jazzson51569 <jazzson51569@153.com>
Refs: #182

Verification:

- pnpm typecheck

- pnpm --filter @prompthub/desktop test -- tests/unit/main/prompt-output-format-db.test.ts tests/unit/services/database-backup-format.test.ts tests/unit/services/database-backup.test.ts --run

- git diff --check
@legeling legeling marked this pull request as ready for review July 9, 2026 11:12
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add persistent prompt output-format sequences (multi-prompt copy)

✨ Enhancement 🧪 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Add persistent output-format sequences to copy multiple prompts as one clipboard payload.
• Wire CRUD + reorder through IPC/preload/renderer store and add a prompt detail panel.
• Include sequences in backup/export/import validation, i18n strings, and regression tests.
Diagram

graph TD
  UI["Prompt detail UI"] --> Store["Prompt store"] --> RDB["Renderer DB svc"] --> Preload["Preload API"] --> IPC["IPC handlers"] --> OFDB["OutputFormat DB"] --> SQLite[("SQLite")]
  Backup["Backup import/export"] --> RDB
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Model sequences as PromptRelation kind
  • ➕ Reuses existing relations table + IPC/store patterns
  • ➕ Avoids adding a new table and DB class
  • ➖ Harder to enforce 'single self item' invariant and stable ordering
  • ➖ Conflates semantic prompt relationships with copy/output behavior
  • ➖ Would require additional ordering metadata anyway (another table or extra columns)
2. Store sequence as a JSON field on Prompt
  • ➕ Fewer tables; sequence travels with prompt record
  • ➕ Potentially simpler backup/export (already exports prompts)
  • ➖ Requires prompt schema changes/migrations and careful merge semantics
  • ➖ Harder to reference other prompts safely with FK constraints/cascade deletes
  • ➖ Ordering updates become read-modify-write on the prompt row (more conflict-prone)
3. Compute output sequence dynamically from folder/relationship graph
  • ➕ No new persistence layer
  • ➖ Doesn't meet the requirement for explicit, user-controlled sequences
  • ➖ Non-deterministic ordering and poor UX for configuration

Recommendation: Current approach (a dedicated prompt_output_format_items table + dedicated DB/IPC surface) is the best fit: it cleanly separates concerns from prompt relationships, supports deterministic ordering, and allows SQLite to enforce invariants (unique self-row via partial index) and cascade cleanup. The backup/import/export inclusion and orphan filtering are also easier to reason about with a first-class record type.

Files changed (34) +1750 / -29

Enhancement (17) +1276 / -22
index.tsExpose PromptOutputFormatDB from desktop DB entrypoint +1/-0

Expose PromptOutputFormatDB from desktop DB entrypoint

• Exports the new PromptOutputFormatDB class from the desktop main-process database module so IPC handlers can construct it alongside existing DB helpers.

apps/desktop/src/main/database/index.ts

index.tsRegister output-format channels as rebindable IPC routes +5/-0

Register output-format channels as rebindable IPC routes

• Adds output-format CRUD + reorder IPC channels to the list of database-backed channels that can be rebound across DB instances.

apps/desktop/src/main/ipc/index.ts

prompt.ipc.tsAdd main-process IPC handlers for output-format items +37/-1

Add main-process IPC handlers for output-format items

• Creates a PromptOutputFormatDB instance and registers IPC handlers to create/list/update/delete/reorder output-format items. Syncs the prompt workspace after mutations to keep renderer state consistent.

apps/desktop/src/main/ipc/prompt.ipc.ts

prompt.tsExpose output-format APIs in preload promptApi +13/-0

Expose output-format APIs in preload promptApi

• Adds preload methods that invoke the new promptOutputFormat IPC channels for CRUD and reorder operations.

apps/desktop/src/preload/api/prompt.ts

MainContent.tsxEnable multi-prompt copy using configured output-format sequences +142/-3

Enable multi-prompt copy using configured output-format sequences

• Extends prompt copy behavior to resolve an ordered queue of prompts based on outputFormatItems, collecting variable-filled results per item and finally copying a blank-line-joined payload. Wires the new PromptOutputFormatPanel into the prompt detail view and tracks counts/toggles in local state.

apps/desktop/src/renderer/components/layout/MainContent.tsx

PromptDetailMetadata.tsxAdd metadata-row toggle for Custom Output Format panel +65/-15

Add metadata-row toggle for Custom Output Format panel

• Adds a new button (with count + expanded state) to open/close the output-format configuration panel from the prompt detail metadata row.

apps/desktop/src/renderer/components/prompt/PromptDetailMetadata.tsx

PromptOutputFormatPanel.tsxNew prompt detail panel to configure output-format sequences +536/-0

New prompt detail panel to configure output-format sequences

• Introduces a search/add/remove UI for output-format items, ensures a self entry exists when adding other prompts, and supports drag-and-drop reordering. Integrates with store callbacks and uses i18n keys for all user-facing strings.

apps/desktop/src/renderer/components/prompt/PromptOutputFormatPanel.tsx

BackupImportConfirmDialog.tsxShow output-format item counts in backup import confirmation +1/-1

Show output-format item counts in backup import confirmation

• Updates the import preview text to include outputFormatItems in the summary counts displayed before import.

apps/desktop/src/renderer/components/settings/BackupImportConfirmDialog.tsx

useBackupImportController.tsxInclude output-format items in skipped-import details formatting +3/-0

Include output-format items in skipped-import details formatting

• Extends skipped stats formatting so import logs can report dropped output format items.

apps/desktop/src/renderer/hooks/useBackupImportController.tsx

database-backup-format.tsExtend backup format/parser to support outputFormatItems +55/-0

Extend backup format/parser to support outputFormatItems

• Adds outputFormatItems to the backup payload, validates shapes, and filters orphaned items referencing missing prompts. Updates skipped-stats tracking and envelope parsing to recognize the new field.

apps/desktop/src/renderer/services/database-backup-format.ts

database-backup.tsRound-trip outputFormatItems in export/import flows +36/-0

Round-trip outputFormatItems in export/import flows

• Exports output format items (and includes them in selective prompt exports) and restores them during import, both via main-process and renderer-side paths. Updates preview/import summary counts and records restore failures per item.

apps/desktop/src/renderer/services/database-backup.ts

database.tsAdd renderer-side database helpers for output-format items +55/-0

Add renderer-side database helpers for output-format items

• Adds create/list/update/delete/reorder helpers that call into the preload prompt API, with graceful fallbacks when the desktop API is unavailable.

apps/desktop/src/renderer/services/database.ts

prompt.store.tsStore and mutate outputFormatItems alongside prompts/relations +46/-2

Store and mutate outputFormatItems alongside prompts/relations

• Extends the prompt store state to load outputFormatItems during fetch, and adds actions to create/delete/reorder items while scheduling persistence sync. Ensures outputFormatItems are cleaned up when prompts are deleted.

apps/desktop/src/renderer/stores/prompt.store.ts

index.tsExport PromptOutputFormatDB from db package +1/-0

Export PromptOutputFormatDB from db package

• Makes the new DB class available to desktop/main consumers via the db package index.

packages/db/src/index.ts

prompt-output-format.tsImplement PromptOutputFormatDB (CRUD + reorder + sort normalization) +252/-0

Implement PromptOutputFormatDB (CRUD + reorder + sort normalization)

• Introduces a DB access layer for prompt_output_format_items with duplicate detection, reorder logic that shifts surrounding items, and post-delete sort normalization. Maps DB rows to shared OutputFormatItem types and enforces required input normalization.

packages/db/src/prompt-output-format.ts

ipc-channels.tsDefine IPC channels for prompt output-format operations +5/-0

Define IPC channels for prompt output-format operations

• Adds channel constants for create/list/update/delete/reorder so renderer/preload/main can share a single contract.

packages/shared/constants/ipc-channels.ts

prompt.tsAdd shared types for OutputFormatItem and DTO/query shapes +23/-0

Add shared types for OutputFormatItem and DTO/query shapes

• Introduces OutputFormatItem plus create/update DTOs and list query types to standardize persistence and IPC payloads across layers.

packages/shared/types/prompt.ts

Tests (4) +232 / -0
data-settings.test.tsxUpdate backup preview/restore mocks for outputFormatItems counts +2/-0

Update backup preview/restore mocks for outputFormatItems counts

• Adjusts test expectations and mocked preview/restore responses to include outputFormatItems in summary counts.

apps/desktop/tests/unit/components/data-settings.test.tsx

prompt-output-format-db.test.tsAdd PromptOutputFormatDB unit tests +144/-0

Add PromptOutputFormatDB unit tests

• Adds coverage for ordered creation, duplicate handling, DB-level self uniqueness, reorder/delete normalization, and cascade deletion behavior via prompt deletes.

apps/desktop/tests/unit/main/prompt-output-format-db.test.ts

database-backup-format.test.tsTest backup parser filtering for orphaned outputFormatItems +63/-0

Test backup parser filtering for orphaned outputFormatItems

• Adds regression coverage ensuring the lenient parser drops outputFormatItems that reference missing prompts and increments skipped stats.

apps/desktop/tests/unit/services/database-backup-format.test.ts

database-backup.test.tsInclude outputFormatItems in backup export/import tests +23/-0

Include outputFormatItems in backup export/import tests

• Updates mocks and assertions so export includes outputFormatItems and import restores them via createOutputFormatItem calls.

apps/desktop/tests/unit/services/database-backup.test.ts

Documentation (12) +223 / -7
de.jsonAdd Custom Output Format translations (de) +12/-1

Add Custom Output Format translations (de)

• Adds localized strings for the custom output format UI and adjusts JSON formatting for the batch delete line.

apps/desktop/src/renderer/i18n/locales/de.json

en.jsonAdd Custom Output Format strings (en) +12/-1

Add Custom Output Format strings (en)

• Adds English strings for the custom output format UI and adjusts JSON formatting for the batch delete line.

apps/desktop/src/renderer/i18n/locales/en.json

es.jsonAdd Custom Output Format translations (es) +12/-1

Add Custom Output Format translations (es)

• Adds localized strings for the custom output format UI and adjusts JSON formatting for the batch delete line.

apps/desktop/src/renderer/i18n/locales/es.json

fr.jsonAdd Custom Output Format translations (fr) +12/-1

Add Custom Output Format translations (fr)

• Adds localized strings for the custom output format UI and adjusts JSON formatting for the batch delete line.

apps/desktop/src/renderer/i18n/locales/fr.json

ja.jsonAdd Custom Output Format translations (ja) +12/-1

Add Custom Output Format translations (ja)

• Adds localized strings for the custom output format UI and adjusts JSON formatting for the batch delete line.

apps/desktop/src/renderer/i18n/locales/ja.json

zh-TW.jsonAdd Custom Output Format translations (zh-TW) +12/-1

Add Custom Output Format translations (zh-TW)

• Adds localized strings for the custom output format UI and adjusts JSON formatting for the batch delete line.

apps/desktop/src/renderer/i18n/locales/zh-TW.json

zh.jsonAdd Custom Output Format translations (zh) +12/-1

Add Custom Output Format translations (zh)

• Adds localized strings for the custom output format UI and adjusts JSON formatting for the batch delete line.

apps/desktop/src/renderer/i18n/locales/zh.json

design.mdDocument design for output-format sequence feature +46/-0

Document design for output-format sequence feature

• Adds design notes covering data model, invariants, backup boundary, IPC contract, and copy behavior expectations.

spec/changes/active/prompt-output-format-contribution/design.md

implementation.mdRecord implementation summary and verification commands +23/-0

Record implementation summary and verification commands

• Adds an implementation log describing what was ported, what was added, and how it was verified.

spec/changes/active/prompt-output-format-contribution/implementation.md

proposal.mdAdd feature proposal and scope/risk notes +25/-0

Add feature proposal and scope/risk notes

• Adds a proposal describing goals, non-goals, and risks for the upstream port of the feature.

spec/changes/active/prompt-output-format-contribution/proposal.md

spec.mdAdd product spec requirements for output-format sequences +31/-0

Add product spec requirements for output-format sequences

• Defines requirements and scenarios for persistence, copy behavior, variable-modal compatibility, and deletion cleanup.

spec/changes/active/prompt-output-format-contribution/specs/prompt/spec.md

tasks.mdTrack implementation tasks for the contribution port +14/-0

Track implementation tasks for the contribution port

• Adds a checked task list for schema, IPC wiring, UI, backup coverage, and verification steps.

spec/changes/active/prompt-output-format-contribution/tasks.md

Other (1) +19 / -0
schema.tsAdd prompt_output_format_items table + indexes/constraints +19/-0

Add prompt_output_format_items table + indexes/constraints

• Adds a new table for ordered output-format items with FK cascade cleanup, a uniqueness constraint per (source,target), and a partial unique index to enforce a single self (NULL target) row per source prompt. Adds supporting indexes for lookups.

packages/db/src/schema.ts

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (1) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 23 rules

Grey Divider


Action required

1. Skipped-details string not translated 📘 Rule violation ✧ Quality
Description
formatImportSkippedDetails() and the import preview/summary counts line both build user-facing
text with hardcoded English labels (including the newly added output format items) instead of
using t(...). This creates partially untranslated UI content (even when embedded in a translated
toast) and violates the react-i18next localization requirement.
Code

apps/desktop/src/renderer/hooks/useBackupImportController.tsx[R29-31]

+    skipped.outputFormatItems > 0
+      ? `output format items: ${skipped.outputFormatItems}`
+      : null,
Evidence
PR Compliance ID 77099 requires all user-facing strings to be internationalized via
useTranslation/t(...). The cited changes introduce a new hardcoded English fragment `output
format items: ${...} inside formatImportSkippedDetails()`, which is interpolated into
t("toast.importPartialSuccess", { details: ... }) and shown to users via the confirmImport
toast, leaving part of the message untranslated; additionally, the import preview sentence in
BackupImportConfirmDialog renders hardcoded English labels such as prompts, folders,
versions, and output format items directly in JSX rather than sourcing them through t(...).

Rule 77099: Internationalize React user-facing text with react-i18next useTranslation hook
apps/desktop/src/renderer/hooks/useBackupImportController.tsx[22-38]
apps/desktop/src/renderer/hooks/useBackupImportController.tsx[99-104]
apps/desktop/src/renderer/components/settings/BackupImportConfirmDialog.tsx[43-43]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
User-facing import details and summary text are constructed with hardcoded English labels (including `output format items`) in `formatImportSkippedDetails()` and in the import preview/summary JSX, resulting in partially untranslated UI and non-compliance with the requirement to use react-i18next `t(...)` for user-visible strings.

## Issue Context
`formatImportSkippedDetails()` produces a details string that is interpolated into `t("toast.importPartialSuccess", { details: ... })` and displayed via a toast in `confirmImport`, so any interpolated fragments must also be localized. Separately, `BackupImportConfirmDialog` renders an import summary/counts sentence directly in the UI and must be fully localizable rather than mixing translated and hardcoded English nouns.

## Fix Focus Areas
- apps/desktop/src/renderer/hooks/useBackupImportController.tsx[22-39]
- apps/desktop/src/renderer/hooks/useBackupImportController.tsx[99-104]
- apps/desktop/src/renderer/components/settings/BackupImportConfirmDialog.tsx[43-43]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Output format order breaks 🐞 Bug ≡ Correctness
Description
createOutputFormatItem prepends the newly created item into outputFormatItems, but
PromptOutputFormatPanel renders and computes drag/drop indices from the array order without
sorting by sortOrder, so new items (typically last by sortOrder) can appear at the top and
reorder operations can send incorrect newSortOrder values.
Code

apps/desktop/src/renderer/stores/prompt.store.ts[R239-246]

+      createOutputFormatItem: async (data) => {
+        const item = await db.createOutputFormatItem(data);
+        set((state) => ({
+          outputFormatItems: [
+            item,
+            ...state.outputFormatItems.filter((existing) => existing.id !== item.id),
+          ],
+        }));
Evidence
The store prepends the created item, but the panel does not sort by sortOrder and uses array index
for drag/drop reorder; meanwhile the DB returns items ordered by sort_order, so the store’s
ordering can drift from the canonical order used by the DB and copy logic.

apps/desktop/src/renderer/stores/prompt.store.ts[234-249]
apps/desktop/src/renderer/components/prompt/PromptOutputFormatPanel.tsx[41-58]
apps/desktop/src/renderer/components/prompt/PromptOutputFormatPanel.tsx[183-207]
packages/db/src/prompt-output-format.ts[129-147]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`outputFormatItems` is treated as already ordered by `sortOrder` in the Output Format panel, but the store currently prepends created items. This makes the rendered list order diverge from persisted `sortOrder`, and drag-and-drop reorder uses `findIndex()` on this unsorted list to compute the `newSortOrder`, causing incorrect reorders.

### Issue Context
- DB `list()` is ordered by `sort_order ASC, created_at ASC`.
- The panel does not sort before rendering/reordering.

### Fix
Make `outputFormatItems` ordering consistent everywhere:
1) In the store, after `createOutputFormatItem`, either:
  - call `fetchOutputFormatItems()` (preferred for correctness), **or**
  - insert the new item into the array in `sortOrder` order.
2) Additionally (defense-in-depth), sort the panel’s `formatItems` by `item.sortOrder` then `item.createdAt` before rendering and before computing `targetIndex`.

### Fix Focus Areas
- apps/desktop/src/renderer/stores/prompt.store.ts[234-264]
- apps/desktop/src/renderer/components/prompt/PromptOutputFormatPanel.tsx[41-58]
- apps/desktop/src/renderer/components/prompt/PromptOutputFormatPanel.tsx[183-207]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. Self selectable as target 🐞 Bug ≡ Correctness
Description
PromptOutputFormatPanel does not always exclude currentPrompt.id from search candidates, so
users can add the current prompt as targetPromptId, producing a non-NULL self-target row that is
rendered as a normal (non-self) entry and can duplicate the source prompt in the configured
sequence.
Code

apps/desktop/src/renderer/components/prompt/PromptOutputFormatPanel.tsx[R87-146]

+  const existingTargetIds = useMemo(() => {
+    const ids = new Set<string>();
+    for (const item of formatItems) {
+      if (item.item.targetPromptId) {
+        ids.add(item.item.targetPromptId);
+      } else {
+        ids.add(currentPrompt.id);
+      }
+    }
+    return ids;
+  }, [currentPrompt.id, formatItems]);
+
+  const candidatePrompts = useMemo(
+    () => prompts.filter((prompt) => !existingTargetIds.has(prompt.id)),
+    [existingTargetIds, prompts],
+  );
+
+  const searchResults = useMemo(() => {
+    if (!query.trim()) return [];
+    const lowerQuery = query.toLowerCase();
+    return candidatePrompts.filter(
+      (prompt) =>
+        prompt.title.toLowerCase().includes(lowerQuery) ||
+        prompt.description?.toLowerCase().includes(lowerQuery) ||
+        prompt.tags.some((tag) => tag.toLowerCase().includes(lowerQuery)),
+    );
+  }, [candidatePrompts, query]);
+
+  const handleAddSelf = useCallback(async () => {
+    if (savingTargetId) return;
+    if (!currentPrompt.id) return;
+    setSavingTargetId("self");
+    try {
+      await onCreateOutputFormatItem({
+        sourcePromptId: currentPrompt.id,
+        targetPromptId: null,
+      });
+    } finally {
+      setSavingTargetId(null);
+    }
+  }, [currentPrompt.id, onCreateOutputFormatItem, savingTargetId]);
+
+  const handleAddPrompt = useCallback(
+    async (targetPromptId: string) => {
+      if (savingTargetId) return;
+      if (!currentPrompt.id || !targetPromptId) return;
+      setSavingTargetId(targetPromptId);
+      try {
+        const hasSelf = existingTargetIds.has(currentPrompt.id);
+        if (!hasSelf) {
+          await onCreateOutputFormatItem({
+            sourcePromptId: currentPrompt.id,
+            targetPromptId: null,
+          });
+        }
+        await onCreateOutputFormatItem({
+          sourcePromptId: currentPrompt.id,
+          targetPromptId,
+        });
+        setQuery("");
Evidence
The panel’s candidates are only filtered by existingTargetIds, so the current prompt can remain
selectable before a self row exists; adding it creates a row where targetPromptId is a string
equal to the source prompt ID, and the UI only treats targetPromptId === null as self. The DB
layer currently normalizes only undefined -> null, not source==target.

apps/desktop/src/renderer/components/prompt/PromptOutputFormatPanel.tsx[87-146]
apps/desktop/src/renderer/components/prompt/PromptOutputFormatPanel.tsx[41-58]
packages/db/src/prompt-output-format.ts[172-186]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The output format panel can allow adding the current prompt as a target prompt (i.e., `targetPromptId === currentPrompt.id`) when no self item exists yet. This creates redundant/ambiguous “self” entries because the UI only treats `targetPromptId === null` as self.

### Issue Context
- `candidatePrompts` only filters by `existingTargetIds`, which does not include `currentPrompt.id` until a NULL-target self row exists.
- DB `normalizeCreateInput()` does not coerce or reject `targetPromptId === sourcePromptId`.

### Fix
Implement both UI and DB guardrails:
1) In `PromptOutputFormatPanel`, exclude the current prompt from `candidatePrompts` unconditionally (e.g., `prompt.id !== currentPrompt.id`).
2) In `PromptOutputFormatDB.normalizeCreateInput`, treat `targetPromptId === sourcePromptId` as `null` (or throw) to enforce the intended representation of “self”.

### Fix Focus Areas
- apps/desktop/src/renderer/components/prompt/PromptOutputFormatPanel.tsx[87-146]
- packages/db/src/prompt-output-format.ts[172-186]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment on lines +29 to +31
skipped.outputFormatItems > 0
? `output format items: ${skipped.outputFormatItems}`
: null,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Skipped-details string not translated 📘 Rule violation ✧ Quality

formatImportSkippedDetails() and the import preview/summary counts line both build user-facing
text with hardcoded English labels (including the newly added output format items) instead of
using t(...). This creates partially untranslated UI content (even when embedded in a translated
toast) and violates the react-i18next localization requirement.
Agent Prompt
## Issue description
User-facing import details and summary text are constructed with hardcoded English labels (including `output format items`) in `formatImportSkippedDetails()` and in the import preview/summary JSX, resulting in partially untranslated UI and non-compliance with the requirement to use react-i18next `t(...)` for user-visible strings.

## Issue Context
`formatImportSkippedDetails()` produces a details string that is interpolated into `t("toast.importPartialSuccess", { details: ... })` and displayed via a toast in `confirmImport`, so any interpolated fragments must also be localized. Separately, `BackupImportConfirmDialog` renders an import summary/counts sentence directly in the UI and must be fully localizable rather than mixing translated and hardcoded English nouns.

## Fix Focus Areas
- apps/desktop/src/renderer/hooks/useBackupImportController.tsx[22-39]
- apps/desktop/src/renderer/hooks/useBackupImportController.tsx[99-104]
- apps/desktop/src/renderer/components/settings/BackupImportConfirmDialog.tsx[43-43]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +239 to +246
createOutputFormatItem: async (data) => {
const item = await db.createOutputFormatItem(data);
set((state) => ({
outputFormatItems: [
item,
...state.outputFormatItems.filter((existing) => existing.id !== item.id),
],
}));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Output format order breaks 🐞 Bug ≡ Correctness

createOutputFormatItem prepends the newly created item into outputFormatItems, but
PromptOutputFormatPanel renders and computes drag/drop indices from the array order without
sorting by sortOrder, so new items (typically last by sortOrder) can appear at the top and
reorder operations can send incorrect newSortOrder values.
Agent Prompt
### Issue description
`outputFormatItems` is treated as already ordered by `sortOrder` in the Output Format panel, but the store currently prepends created items. This makes the rendered list order diverge from persisted `sortOrder`, and drag-and-drop reorder uses `findIndex()` on this unsorted list to compute the `newSortOrder`, causing incorrect reorders.

### Issue Context
- DB `list()` is ordered by `sort_order ASC, created_at ASC`.
- The panel does not sort before rendering/reordering.

### Fix
Make `outputFormatItems` ordering consistent everywhere:
1) In the store, after `createOutputFormatItem`, either:
   - call `fetchOutputFormatItems()` (preferred for correctness), **or**
   - insert the new item into the array in `sortOrder` order.
2) Additionally (defense-in-depth), sort the panel’s `formatItems` by `item.sortOrder` then `item.createdAt` before rendering and before computing `targetIndex`.

### Fix Focus Areas
- apps/desktop/src/renderer/stores/prompt.store.ts[234-264]
- apps/desktop/src/renderer/components/prompt/PromptOutputFormatPanel.tsx[41-58]
- apps/desktop/src/renderer/components/prompt/PromptOutputFormatPanel.tsx[183-207]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +87 to +146
const existingTargetIds = useMemo(() => {
const ids = new Set<string>();
for (const item of formatItems) {
if (item.item.targetPromptId) {
ids.add(item.item.targetPromptId);
} else {
ids.add(currentPrompt.id);
}
}
return ids;
}, [currentPrompt.id, formatItems]);

const candidatePrompts = useMemo(
() => prompts.filter((prompt) => !existingTargetIds.has(prompt.id)),
[existingTargetIds, prompts],
);

const searchResults = useMemo(() => {
if (!query.trim()) return [];
const lowerQuery = query.toLowerCase();
return candidatePrompts.filter(
(prompt) =>
prompt.title.toLowerCase().includes(lowerQuery) ||
prompt.description?.toLowerCase().includes(lowerQuery) ||
prompt.tags.some((tag) => tag.toLowerCase().includes(lowerQuery)),
);
}, [candidatePrompts, query]);

const handleAddSelf = useCallback(async () => {
if (savingTargetId) return;
if (!currentPrompt.id) return;
setSavingTargetId("self");
try {
await onCreateOutputFormatItem({
sourcePromptId: currentPrompt.id,
targetPromptId: null,
});
} finally {
setSavingTargetId(null);
}
}, [currentPrompt.id, onCreateOutputFormatItem, savingTargetId]);

const handleAddPrompt = useCallback(
async (targetPromptId: string) => {
if (savingTargetId) return;
if (!currentPrompt.id || !targetPromptId) return;
setSavingTargetId(targetPromptId);
try {
const hasSelf = existingTargetIds.has(currentPrompt.id);
if (!hasSelf) {
await onCreateOutputFormatItem({
sourcePromptId: currentPrompt.id,
targetPromptId: null,
});
}
await onCreateOutputFormatItem({
sourcePromptId: currentPrompt.id,
targetPromptId,
});
setQuery("");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

3. Self selectable as target 🐞 Bug ≡ Correctness

PromptOutputFormatPanel does not always exclude currentPrompt.id from search candidates, so
users can add the current prompt as targetPromptId, producing a non-NULL self-target row that is
rendered as a normal (non-self) entry and can duplicate the source prompt in the configured
sequence.
Agent Prompt
### Issue description
The output format panel can allow adding the current prompt as a target prompt (i.e., `targetPromptId === currentPrompt.id`) when no self item exists yet. This creates redundant/ambiguous “self” entries because the UI only treats `targetPromptId === null` as self.

### Issue Context
- `candidatePrompts` only filters by `existingTargetIds`, which does not include `currentPrompt.id` until a NULL-target self row exists.
- DB `normalizeCreateInput()` does not coerce or reject `targetPromptId === sourcePromptId`.

### Fix
Implement both UI and DB guardrails:
1) In `PromptOutputFormatPanel`, exclude the current prompt from `candidatePrompts` unconditionally (e.g., `prompt.id !== currentPrompt.id`).
2) In `PromptOutputFormatDB.normalizeCreateInput`, treat `targetPromptId === sourcePromptId` as `null` (or throw) to enforce the intended representation of “self”.

### Fix Focus Areas
- apps/desktop/src/renderer/components/prompt/PromptOutputFormatPanel.tsx[87-146]
- packages/db/src/prompt-output-format.ts[172-186]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/desktop/src/renderer/services/database-backup.ts (1)

750-793: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

importDatabaseViaMainProcess 未检查 createOutputFormat API 可用性

函数顶部的可用性检查(Lines 750-758)未包含 window.api?.prompt?.createOutputFormat。当该 API 不可用时(例如版本不匹配),函数会继续执行:删除现有 prompts/folders → 插入新数据 → 调用 createOutputFormatItem 时抛出异常。这导致现有数据已删除、新数据部分恢复,但 outputFormatItems 创建失败,且非主进程回退路径不会执行。

createOutputFormat 加入可用性检查后,函数会返回 false,回退到非主进程路径(Lines 935-949),该路径有 try/catch 可优雅处理失败。

🔒 建议修复:添加 createOutputFormat 到可用性检查
   if (
     !window.api?.prompt?.getAll ||
     !window.api?.prompt?.delete ||
     !window.api?.prompt?.insertDirect ||
+    !window.api?.prompt?.createOutputFormat ||
     !window.api?.folder?.getAll ||
     !window.api?.folder?.delete ||
     !window.api?.folder?.insertDirect ||
     !window.api?.version?.insertDirect
   ) {
     return false;
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/renderer/services/database-backup.ts` around lines 750 -
793, `importDatabaseViaMainProcess` 的前置可用性检查缺少
`window.api?.prompt?.createOutputFormat`,导致在该 API 不可用时仍继续执行恢复流程并可能在
`createOutputFormatItem` 处失败。请在函数开头的能力检查中补上 `createOutputFormat`,让不兼容版本直接返回
`false`,从而回退到带 try/catch 的非主进程恢复路径,避免在 `existingPrompts`、`existingFolders`
清理后出现部分恢复状态。
🧹 Nitpick comments (2)
apps/desktop/src/renderer/components/prompt/PromptOutputFormatPanel.tsx (1)

373-383: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

条目重排仅通过 HTML5 拖拽实现,没有键盘可达的替代方式(拖拽手柄为 aria-hidden,行容器无 role/键盘处理)。键盘用户可以添加/删除但无法重排。建议后续补充键盘重排(例如上/下移动按钮或方向键处理),使该交互对键盘/辅助技术可用。

As per coding guidelines: "All interactive elements must have appropriate ARIA labels".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/renderer/components/prompt/PromptOutputFormatPanel.tsx`
around lines 373 - 383, The item reordering in PromptOutputFormatPanel is only
available via HTML5 drag-and-drop, leaving keyboard and assistive-technology
users unable to reorder items. Update the draggable row in
PromptOutputFormatPanel to provide a keyboard-accessible alternative, such as
explicit move up/down controls or keyboard handlers on the item container, and
ensure any interactive controls have appropriate ARIA labels while keeping the
existing onDragStart/onDrop behavior intact.

Source: Coding guidelines

packages/db/src/prompt-output-format.ts (1)

62-81: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

update 方法(仅更新 sort_order)在测试套件中完全没有被覆盖到,包括 getById 返回 null 的分支与 data.sortOrder !== undefined ? … : existing.sortOrder 两个分支。作为数据库边界模块,建议补充针对 update 的单元测试。

As per coding guidelines: critical boundary modules (database …) "require 100% branch and condition coverage for changed behavior before merging".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/db/src/prompt-output-format.ts` around lines 62 - 81, The `update`
method in `PromptOutputFormat` has changed behavior but lacks test coverage for
both the `getById` null path and the `data.sortOrder !== undefined ? ... :
existing.sortOrder` branch. Add unit tests for `update` that verify it returns
null when `getById` finds no record, and that it preserves `existing.sortOrder`
when `sortOrder` is omitted while using the provided value when present. Ensure
the tests exercise the database boundary behavior through `update`, `getById`,
and the `prompt_output_format_items` update path.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/desktop/src/main/ipc/prompt.ipc.ts`:
- Around line 313-344: The new output-format IPC handlers in prompt.ipc.ts need
upfront validation and error handling before touching outputFormatDb. Add input
checks in the PROMPT_OUTPUT_FORMAT_CREATE/UPDATE/DELETE/REORDER handlers (and
reuse the same style as assertPromptMoveInput) to verify ids are non-empty
strings, payloads are objects, and sort orders are finite non-negative numbers,
then reject malformed requests early. Wrap each ipcMain.handle callback in
try/catch so DB exceptions are converted into structured error responses instead
of propagating silently; keep the syncWorkspace calls only after successful
mutations.

In `@apps/desktop/src/renderer/components/settings/BackupImportConfirmDialog.tsx`:
- Line 43: The BackupImportConfirmDialog render text still hardcodes the "output
format items" label instead of routing it through i18n. Update the JSX in
BackupImportConfirmDialog to use t() for that label, following the existing
translation pattern used around importPreview.summary.counts, and add or reuse a
translation key so all user-visible text in this component is localized
consistently.

In `@apps/desktop/src/renderer/hooks/useBackupImportController.tsx`:
- Around line 29-31: The user-visible label in formatImportSkippedDetails is
hardcoded English and must be localized. Update the helper so the “output format
items” text goes through translation by either passing t into
formatImportSkippedDetails or using i18next.t() inside it, and keep the existing
skipped.outputFormatItems check unchanged. Make sure the returned string list in
useBackupImportController uses translated text for this label rather than a
literal.

In `@packages/db/src/prompt-output-format.ts`:
- Around line 83-120: The reorder flow in prompt-output-format’s reorder method
performs multiple UPDATE statements without atomic protection, so wrap the shift
UPDATE and final item UPDATE in db.transaction() to keep sort_order consistent
if any step fails. Apply the same transaction pattern to the multi-statement
delete path and normalizeSortOrder helper as well, using their existing method
names to locate the write sequences.

---

Outside diff comments:
In `@apps/desktop/src/renderer/services/database-backup.ts`:
- Around line 750-793: `importDatabaseViaMainProcess` 的前置可用性检查缺少
`window.api?.prompt?.createOutputFormat`,导致在该 API 不可用时仍继续执行恢复流程并可能在
`createOutputFormatItem` 处失败。请在函数开头的能力检查中补上 `createOutputFormat`,让不兼容版本直接返回
`false`,从而回退到带 try/catch 的非主进程恢复路径,避免在 `existingPrompts`、`existingFolders`
清理后出现部分恢复状态。

---

Nitpick comments:
In `@apps/desktop/src/renderer/components/prompt/PromptOutputFormatPanel.tsx`:
- Around line 373-383: The item reordering in PromptOutputFormatPanel is only
available via HTML5 drag-and-drop, leaving keyboard and assistive-technology
users unable to reorder items. Update the draggable row in
PromptOutputFormatPanel to provide a keyboard-accessible alternative, such as
explicit move up/down controls or keyboard handlers on the item container, and
ensure any interactive controls have appropriate ARIA labels while keeping the
existing onDragStart/onDrop behavior intact.

In `@packages/db/src/prompt-output-format.ts`:
- Around line 62-81: The `update` method in `PromptOutputFormat` has changed
behavior but lacks test coverage for both the `getById` null path and the
`data.sortOrder !== undefined ? ... : existing.sortOrder` branch. Add unit tests
for `update` that verify it returns null when `getById` finds no record, and
that it preserves `existing.sortOrder` when `sortOrder` is omitted while using
the provided value when present. Ensure the tests exercise the database boundary
behavior through `update`, `getById`, and the `prompt_output_format_items`
update path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b78c102a-1377-4443-9d14-74275b904f3b

📥 Commits

Reviewing files that changed from the base of the PR and between a7cc8a2 and d70616f.

📒 Files selected for processing (34)
  • apps/desktop/src/main/database/index.ts
  • apps/desktop/src/main/ipc/index.ts
  • apps/desktop/src/main/ipc/prompt.ipc.ts
  • apps/desktop/src/preload/api/prompt.ts
  • apps/desktop/src/renderer/components/layout/MainContent.tsx
  • apps/desktop/src/renderer/components/prompt/PromptDetailMetadata.tsx
  • apps/desktop/src/renderer/components/prompt/PromptOutputFormatPanel.tsx
  • apps/desktop/src/renderer/components/settings/BackupImportConfirmDialog.tsx
  • apps/desktop/src/renderer/hooks/useBackupImportController.tsx
  • apps/desktop/src/renderer/i18n/locales/de.json
  • apps/desktop/src/renderer/i18n/locales/en.json
  • apps/desktop/src/renderer/i18n/locales/es.json
  • apps/desktop/src/renderer/i18n/locales/fr.json
  • apps/desktop/src/renderer/i18n/locales/ja.json
  • apps/desktop/src/renderer/i18n/locales/zh-TW.json
  • apps/desktop/src/renderer/i18n/locales/zh.json
  • apps/desktop/src/renderer/services/database-backup-format.ts
  • apps/desktop/src/renderer/services/database-backup.ts
  • apps/desktop/src/renderer/services/database.ts
  • apps/desktop/src/renderer/stores/prompt.store.ts
  • apps/desktop/tests/unit/components/data-settings.test.tsx
  • apps/desktop/tests/unit/main/prompt-output-format-db.test.ts
  • apps/desktop/tests/unit/services/database-backup-format.test.ts
  • apps/desktop/tests/unit/services/database-backup.test.ts
  • packages/db/src/index.ts
  • packages/db/src/prompt-output-format.ts
  • packages/db/src/schema.ts
  • packages/shared/constants/ipc-channels.ts
  • packages/shared/types/prompt.ts
  • spec/changes/active/prompt-output-format-contribution/design.md
  • spec/changes/active/prompt-output-format-contribution/implementation.md
  • spec/changes/active/prompt-output-format-contribution/proposal.md
  • spec/changes/active/prompt-output-format-contribution/specs/prompt/spec.md
  • spec/changes/active/prompt-output-format-contribution/tasks.md

Comment on lines +313 to +344

ipcMain.handle(IPC_CHANNELS.PROMPT_OUTPUT_FORMAT_CREATE, async (_, data: CreateOutputFormatItemDTO) => {
const item = outputFormatDb.create(data);
syncWorkspace();
return item;
});

ipcMain.handle(IPC_CHANNELS.PROMPT_OUTPUT_FORMAT_LIST, async (_, query?: OutputFormatItemQuery) => {
return outputFormatDb.list(query);
});

ipcMain.handle(IPC_CHANNELS.PROMPT_OUTPUT_FORMAT_UPDATE, async (_, id: string, data: UpdateOutputFormatItemDTO) => {
const item = outputFormatDb.update(id, data);
if (item) {
syncWorkspace();
}
return item;
});

ipcMain.handle(IPC_CHANNELS.PROMPT_OUTPUT_FORMAT_DELETE, async (_, id: string) => {
const deleted = outputFormatDb.delete(id);
if (deleted) {
syncWorkspace();
}
return deleted;
});

ipcMain.handle(IPC_CHANNELS.PROMPT_OUTPUT_FORMAT_REORDER, async (_, sourcePromptId: string, itemId: string, newSortOrder: number) => {
outputFormatDb.reorder(sourcePromptId, itemId, newSortOrder);
syncWorkspace();
return true;
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

IPC 处理器缺少输入校验,违反编码规范。

新增的五个输出格式 IPC 处理器均未验证输入参数。根据 apps/desktop/src/main/ipc/** 编码规范:"All IPC handlers must validate input types and reject malformed payloads before processing" 以及 "IPC handlers must catch errors and return structured error responses, never crash the main process silently"。

具体问题:

  • CREATE:未校验 data 是否为对象、sourcePromptId 是否为非空字符串、targetPromptId 是否为 string|nullsortOrder 是否为非负数。
  • REORDER:未校验 sourcePromptId/itemId 为非空字符串,未校验 newSortOrder 为有限非负数(对比同文件中 assertPromptMoveInputnewOrder 的校验)。
  • UPDATE/DELETE:未校验 id 为非空字符串。
  • 所有处理器均无 try/catch,DB 层异常直接传播为 IPC rejection。

As per coding guidelines: apps/desktop/src/main/ipc/** — "All IPC handlers must validate input types and reject malformed payloads before processing" and "IPC handlers must catch errors and return structured error responses, never crash the main process silently".

🛡️ 建议添加输入校验函数
+ const assertOutputFormatId = (id: string) => {
+   if (typeof id !== 'string' || id.trim().length === 0) {
+     throw new Error('Output format item id is required');
+   }
+ };
+
+ const assertOutputFormatCreateInput = (data: CreateOutputFormatItemDTO) => {
+   if (!data || typeof data !== 'object') {
+     throw new Error('Output format data is required');
+   }
+   if (typeof data.sourcePromptId !== 'string' || data.sourcePromptId.trim().length === 0) {
+     throw new Error('Source prompt id is required');
+   }
+   if (
+     data.targetPromptId !== null &&
+     (typeof data.targetPromptId !== 'string' || data.targetPromptId.trim().length === 0)
+   ) {
+     throw new Error('Target prompt id must be null or a non-empty string');
+   }
+   if (
+     data.sortOrder !== undefined &&
+     (!Number.isFinite(data.sortOrder) || data.sortOrder < 0)
+   ) {
+     throw new Error('Sort order must be a non-negative number');
+   }
+ };
+
+ const assertOutputFormatReorderInput = (
+   sourcePromptId: string,
+   itemId: string,
+   newSortOrder: number,
+ ) => {
+   if (typeof sourcePromptId !== 'string' || sourcePromptId.trim().length === 0) {
+     throw new Error('Source prompt id is required');
+   }
+   if (typeof itemId !== 'string' || itemId.trim().length === 0) {
+     throw new Error('Item id is required');
+   }
+   if (!Number.isFinite(newSortOrder) || newSortOrder < 0) {
+     throw new Error('Sort order must be a non-negative number');
+   }
+ };

  ipcMain.handle(IPC_CHANNELS.PROMPT_OUTPUT_FORMAT_CREATE, async (_, data: CreateOutputFormatItemDTO) => {
+   assertOutputFormatCreateInput(data);
    const item = outputFormatDb.create(data);
    syncWorkspace();
    return item;
  });

  ipcMain.handle(IPC_CHANNELS.PROMPT_OUTPUT_FORMAT_LIST, async (_, query?: OutputFormatItemQuery) => {
+   if (query !== undefined && (typeof query !== 'object' || query === null)) {
+     throw new Error('Query must be an object or undefined');
+   }
    return outputFormatDb.list(query);
  });

  ipcMain.handle(IPC_CHANNELS.PROMPT_OUTPUT_FORMAT_UPDATE, async (_, id: string, data: UpdateOutputFormatItemDTO) => {
+   assertOutputFormatId(id);
+   if (!data || typeof data !== 'object') {
+     throw new Error('Update data is required');
+   }
+   if (
+     data.sortOrder !== undefined &&
+     (!Number.isFinite(data.sortOrder) || data.sortOrder < 0)
+   ) {
+     throw new Error('Sort order must be a non-negative number');
+   }
    const item = outputFormatDb.update(id, data);
    if (item) {
      syncWorkspace();
    }
    return item;
  });

  ipcMain.handle(IPC_CHANNELS.PROMPT_OUTPUT_FORMAT_DELETE, async (_, id: string) => {
+   assertOutputFormatId(id);
    const deleted = outputFormatDb.delete(id);
    if (deleted) {
      syncWorkspace();
    }
    return deleted;
  });

  ipcMain.handle(IPC_CHANNELS.PROMPT_OUTPUT_FORMAT_REORDER, async (_, sourcePromptId: string, itemId: string, newSortOrder: number) => {
+   assertOutputFormatReorderInput(sourcePromptId, itemId, newSortOrder);
    outputFormatDb.reorder(sourcePromptId, itemId, newSortOrder);
    syncWorkspace();
    return true;
  });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ipcMain.handle(IPC_CHANNELS.PROMPT_OUTPUT_FORMAT_CREATE, async (_, data: CreateOutputFormatItemDTO) => {
const item = outputFormatDb.create(data);
syncWorkspace();
return item;
});
ipcMain.handle(IPC_CHANNELS.PROMPT_OUTPUT_FORMAT_LIST, async (_, query?: OutputFormatItemQuery) => {
return outputFormatDb.list(query);
});
ipcMain.handle(IPC_CHANNELS.PROMPT_OUTPUT_FORMAT_UPDATE, async (_, id: string, data: UpdateOutputFormatItemDTO) => {
const item = outputFormatDb.update(id, data);
if (item) {
syncWorkspace();
}
return item;
});
ipcMain.handle(IPC_CHANNELS.PROMPT_OUTPUT_FORMAT_DELETE, async (_, id: string) => {
const deleted = outputFormatDb.delete(id);
if (deleted) {
syncWorkspace();
}
return deleted;
});
ipcMain.handle(IPC_CHANNELS.PROMPT_OUTPUT_FORMAT_REORDER, async (_, sourcePromptId: string, itemId: string, newSortOrder: number) => {
outputFormatDb.reorder(sourcePromptId, itemId, newSortOrder);
syncWorkspace();
return true;
});
const assertOutputFormatId = (id: string) => {
if (typeof id !== 'string' || id.trim().length === 0) {
throw new Error('Output format item id is required');
}
};
const assertOutputFormatCreateInput = (data: CreateOutputFormatItemDTO) => {
if (!data || typeof data !== 'object') {
throw new Error('Output format data is required');
}
if (typeof data.sourcePromptId !== 'string' || data.sourcePromptId.trim().length === 0) {
throw new Error('Source prompt id is required');
}
if (
data.targetPromptId !== null &&
(typeof data.targetPromptId !== 'string' || data.targetPromptId.trim().length === 0)
) {
throw new Error('Target prompt id must be null or a non-empty string');
}
if (
data.sortOrder !== undefined &&
(!Number.isFinite(data.sortOrder) || data.sortOrder < 0)
) {
throw new Error('Sort order must be a non-negative number');
}
};
const assertOutputFormatReorderInput = (
sourcePromptId: string,
itemId: string,
newSortOrder: number,
) => {
if (typeof sourcePromptId !== 'string' || sourcePromptId.trim().length === 0) {
throw new Error('Source prompt id is required');
}
if (typeof itemId !== 'string' || itemId.trim().length === 0) {
throw new Error('Item id is required');
}
if (!Number.isFinite(newSortOrder) || newSortOrder < 0) {
throw new Error('Sort order must be a non-negative number');
}
};
ipcMain.handle(IPC_CHANNELS.PROMPT_OUTPUT_FORMAT_CREATE, async (_, data: CreateOutputFormatItemDTO) => {
assertOutputFormatCreateInput(data);
const item = outputFormatDb.create(data);
syncWorkspace();
return item;
});
ipcMain.handle(IPC_CHANNELS.PROMPT_OUTPUT_FORMAT_LIST, async (_, query?: OutputFormatItemQuery) => {
if (query !== undefined && (typeof query !== 'object' || query === null)) {
throw new Error('Query must be an object or undefined');
}
return outputFormatDb.list(query);
});
ipcMain.handle(IPC_CHANNELS.PROMPT_OUTPUT_FORMAT_UPDATE, async (_, id: string, data: UpdateOutputFormatItemDTO) => {
assertOutputFormatId(id);
if (!data || typeof data !== 'object') {
throw new Error('Update data is required');
}
if (
data.sortOrder !== undefined &&
(!Number.isFinite(data.sortOrder) || data.sortOrder < 0)
) {
throw new Error('Sort order must be a non-negative number');
}
const item = outputFormatDb.update(id, data);
if (item) {
syncWorkspace();
}
return item;
});
ipcMain.handle(IPC_CHANNELS.PROMPT_OUTPUT_FORMAT_DELETE, async (_, id: string) => {
assertOutputFormatId(id);
const deleted = outputFormatDb.delete(id);
if (deleted) {
syncWorkspace();
}
return deleted;
});
ipcMain.handle(IPC_CHANNELS.PROMPT_OUTPUT_FORMAT_REORDER, async (_, sourcePromptId: string, itemId: string, newSortOrder: number) => {
assertOutputFormatReorderInput(sourcePromptId, itemId, newSortOrder);
outputFormatDb.reorder(sourcePromptId, itemId, newSortOrder);
syncWorkspace();
return true;
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/main/ipc/prompt.ipc.ts` around lines 313 - 344, The new
output-format IPC handlers in prompt.ipc.ts need upfront validation and error
handling before touching outputFormatDb. Add input checks in the
PROMPT_OUTPUT_FORMAT_CREATE/UPDATE/DELETE/REORDER handlers (and reuse the same
style as assertPromptMoveInput) to verify ids are non-empty strings, payloads
are objects, and sort orders are finite non-negative numbers, then reject
malformed requests early. Wrap each ipcMain.handle callback in try/catch so DB
exceptions are converted into structured error responses instead of propagating
silently; keep the syncWorkspace calls only after successful mutations.

Source: Coding guidelines

</p>
<p>
{t("settings.importPreviewCounts", "Will import")}: {importPreview.summary.counts.prompts} prompts, {importPreview.summary.counts.folders} folders, {importPreview.summary.counts.versions} versions, {importPreview.summary.counts.rules} rules, {importPreview.summary.counts.skills} skills
{t("settings.importPreviewCounts", "Will import")}: {importPreview.summary.counts.prompts} prompts, {importPreview.summary.counts.folders} folders, {importPreview.summary.counts.versions} versions, {importPreview.summary.counts.outputFormatItems} output format items, {importPreview.summary.counts.rules} rules, {importPreview.summary.counts.skills} skills

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

"output format items" 文本未通过 i18n 翻译

根据编码规范,渲染层所有用户可见文本必须通过 t() 翻译。新增的 "output format items" 标签为硬编码英文。虽然现有标签("prompts"、"folders" 等)也存在同样问题,但新增代码不应继续违反此规范。

As per coding guidelines: "{apps/desktop/src/renderer/,apps/web/src/client/}: All text visible to users must go through t() from react-i18next; this includes button labels, error messages, placeholders, tooltips, and status text"

🌐 建议修复:使用 t() 翻译标签
-              {t("settings.importPreviewCounts", "Will import")}: {importPreview.summary.counts.prompts} prompts, {importPreview.summary.counts.folders} folders, {importPreview.summary.counts.versions} versions, {importPreview.summary.counts.outputFormatItems} output format items, {importPreview.summary.counts.rules} rules, {importPreview.summary.counts.skills} skills
+              {t("settings.importPreviewCounts", "Will import")}: {importPreview.summary.counts.prompts} {t("settings.importPreviewPrompts", "prompts")}, {importPreview.summary.counts.folders} {t("settings.importPreviewFolders", "folders")}, {importPreview.summary.counts.versions} {t("settings.importPreviewVersions", "versions")}, {importPreview.summary.counts.outputFormatItems} {t("settings.importPreviewOutputFormatItems", "output format items")}, {importPreview.summary.counts.rules} {t("settings.importPreviewRules", "rules")}, {importPreview.summary.counts.skills} {t("settings.importPreviewSkills", "skills")}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{t("settings.importPreviewCounts", "Will import")}: {importPreview.summary.counts.prompts} prompts, {importPreview.summary.counts.folders} folders, {importPreview.summary.counts.versions} versions, {importPreview.summary.counts.outputFormatItems} output format items, {importPreview.summary.counts.rules} rules, {importPreview.summary.counts.skills} skills
{t("settings.importPreviewCounts", "Will import")}: {importPreview.summary.counts.prompts} {t("settings.importPreviewPrompts", "prompts")}, {importPreview.summary.counts.folders} {t("settings.importPreviewFolders", "folders")}, {importPreview.summary.counts.versions} {t("settings.importPreviewVersions", "versions")}, {importPreview.summary.counts.outputFormatItems} {t("settings.importPreviewOutputFormatItems", "output format items")}, {importPreview.summary.counts.rules} {t("settings.importPreviewRules", "rules")}, {importPreview.summary.counts.skills} {t("settings.importPreviewSkills", "skills")}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/renderer/components/settings/BackupImportConfirmDialog.tsx`
at line 43, The BackupImportConfirmDialog render text still hardcodes the
"output format items" label instead of routing it through i18n. Update the JSX
in BackupImportConfirmDialog to use t() for that label, following the existing
translation pattern used around importPreview.summary.counts, and add or reuse a
translation key so all user-visible text in this component is localized
consistently.

Source: Coding guidelines

Comment on lines +29 to +31
skipped.outputFormatItems > 0
? `output format items: ${skipped.outputFormatItems}`
: null,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

formatImportSkippedDetails 中 "output format items" 为硬编码英文

该函数返回的用户可见字符串中,"output format items" 标签未通过 t() 翻译。由于此函数是普通工具函数(非 React 组件),需将 t 作为参数传入或改用 i18next.t() 才能修复。

As per coding guidelines: "{apps/desktop/src/renderer/,apps/web/src/client/}: All text visible to users must go through t() from react-i18next"

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/renderer/hooks/useBackupImportController.tsx` around lines
29 - 31, The user-visible label in formatImportSkippedDetails is hardcoded
English and must be localized. Update the helper so the “output format items”
text goes through translation by either passing t into
formatImportSkippedDetails or using i18next.t() inside it, and keep the existing
skipped.outputFormatItems check unchanged. Make sure the returned string list in
useBackupImportController uses translated text for this label rather than a
literal.

Source: Coding guidelines

Comment on lines +83 to +120
reorder(sourcePromptId: string, itemId: string, newSortOrder: number): void {
const items = this.list({ sourcePromptId });
const itemIndex = items.findIndex((item) => item.id === itemId);
if (itemIndex === -1) return;

const item = items[itemIndex];
const oldOrder = item.sortOrder;

if (newSortOrder === oldOrder) return;

const now = Date.now();

if (newSortOrder > oldOrder) {
this.db
.prepare(
`UPDATE prompt_output_format_items
SET sort_order = sort_order - 1, updated_at = ?
WHERE source_prompt_id = ? AND sort_order > ? AND sort_order <= ?`,
)
.run(now, sourcePromptId, oldOrder, newSortOrder);
} else {
this.db
.prepare(
`UPDATE prompt_output_format_items
SET sort_order = sort_order + 1, updated_at = ?
WHERE source_prompt_id = ? AND sort_order >= ? AND sort_order < ?`,
)
.run(now, sourcePromptId, newSortOrder, oldOrder);
}

this.db
.prepare(
`UPDATE prompt_output_format_items
SET sort_order = ?, updated_at = ?
WHERE id = ?`,
)
.run(newSortOrder, now, itemId);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

reorder 的多条写操作未包裹事务,存在原子性风险。

reorder 先执行一条位移 UPDATE,再执行一条设置目标 sort_orderUPDATEdelete(Line 149-162)在 DELETE 之后调用 normalizeSortOrder,而 normalizeSortOrder(Line 225-240)又在循环里多次 UPDATE。这些多语句写操作若中途失败,会残留错乱的 sort_order。请统一用 db.transaction() 包裹。

🔒 建议为 reorder 包裹事务(delete/normalizeSortOrder 同理)
   reorder(sourcePromptId: string, itemId: string, newSortOrder: number): void {
     const items = this.list({ sourcePromptId });
     const itemIndex = items.findIndex((item) => item.id === itemId);
     if (itemIndex === -1) return;

     const item = items[itemIndex];
     const oldOrder = item.sortOrder;

     if (newSortOrder === oldOrder) return;

     const now = Date.now();

-    if (newSortOrder > oldOrder) {
-      this.db
-        .prepare(
-          `UPDATE prompt_output_format_items
-           SET sort_order = sort_order - 1, updated_at = ?
-           WHERE source_prompt_id = ? AND sort_order > ? AND sort_order <= ?`,
-        )
-        .run(now, sourcePromptId, oldOrder, newSortOrder);
-    } else {
-      this.db
-        .prepare(
-          `UPDATE prompt_output_format_items
-           SET sort_order = sort_order + 1, updated_at = ?
-           WHERE source_prompt_id = ? AND sort_order >= ? AND sort_order < ?`,
-        )
-        .run(now, sourcePromptId, newSortOrder, oldOrder);
-    }
-
-    this.db
-      .prepare(
-        `UPDATE prompt_output_format_items
-         SET sort_order = ?, updated_at = ?
-         WHERE id = ?`,
-      )
-      .run(newSortOrder, now, itemId);
+    this.db.transaction(() => {
+      if (newSortOrder > oldOrder) {
+        this.db
+          .prepare(
+            `UPDATE prompt_output_format_items
+             SET sort_order = sort_order - 1, updated_at = ?
+             WHERE source_prompt_id = ? AND sort_order > ? AND sort_order <= ?`,
+          )
+          .run(now, sourcePromptId, oldOrder, newSortOrder);
+      } else {
+        this.db
+          .prepare(
+            `UPDATE prompt_output_format_items
+             SET sort_order = sort_order + 1, updated_at = ?
+             WHERE source_prompt_id = ? AND sort_order >= ? AND sort_order < ?`,
+          )
+          .run(now, sourcePromptId, newSortOrder, oldOrder);
+      }
+
+      this.db
+        .prepare(
+          `UPDATE prompt_output_format_items
+           SET sort_order = ?, updated_at = ?
+           WHERE id = ?`,
+        )
+        .run(newSortOrder, now, itemId);
+    })();
   }

As per coding guidelines: "Any operation involving multiple SQL statements must be wrapped in db.transaction()".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
reorder(sourcePromptId: string, itemId: string, newSortOrder: number): void {
const items = this.list({ sourcePromptId });
const itemIndex = items.findIndex((item) => item.id === itemId);
if (itemIndex === -1) return;
const item = items[itemIndex];
const oldOrder = item.sortOrder;
if (newSortOrder === oldOrder) return;
const now = Date.now();
if (newSortOrder > oldOrder) {
this.db
.prepare(
`UPDATE prompt_output_format_items
SET sort_order = sort_order - 1, updated_at = ?
WHERE source_prompt_id = ? AND sort_order > ? AND sort_order <= ?`,
)
.run(now, sourcePromptId, oldOrder, newSortOrder);
} else {
this.db
.prepare(
`UPDATE prompt_output_format_items
SET sort_order = sort_order + 1, updated_at = ?
WHERE source_prompt_id = ? AND sort_order >= ? AND sort_order < ?`,
)
.run(now, sourcePromptId, newSortOrder, oldOrder);
}
this.db
.prepare(
`UPDATE prompt_output_format_items
SET sort_order = ?, updated_at = ?
WHERE id = ?`,
)
.run(newSortOrder, now, itemId);
}
reorder(sourcePromptId: string, itemId: string, newSortOrder: number): void {
const items = this.list({ sourcePromptId });
const itemIndex = items.findIndex((item) => item.id === itemId);
if (itemIndex === -1) return;
const item = items[itemIndex];
const oldOrder = item.sortOrder;
if (newSortOrder === oldOrder) return;
const now = Date.now();
this.db.transaction(() => {
if (newSortOrder > oldOrder) {
this.db
.prepare(
`UPDATE prompt_output_format_items
SET sort_order = sort_order - 1, updated_at = ?
WHERE source_prompt_id = ? AND sort_order > ? AND sort_order <= ?`,
)
.run(now, sourcePromptId, oldOrder, newSortOrder);
} else {
this.db
.prepare(
`UPDATE prompt_output_format_items
SET sort_order = sort_order + 1, updated_at = ?
WHERE source_prompt_id = ? AND sort_order >= ? AND sort_order < ?`,
)
.run(now, sourcePromptId, newSortOrder, oldOrder);
}
this.db
.prepare(
`UPDATE prompt_output_format_items
SET sort_order = ?, updated_at = ?
WHERE id = ?`,
)
.run(newSortOrder, now, itemId);
})();
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/db/src/prompt-output-format.ts` around lines 83 - 120, The reorder
flow in prompt-output-format’s reorder method performs multiple UPDATE
statements without atomic protection, so wrap the shift UPDATE and final item
UPDATE in db.transaction() to keep sort_order consistent if any step fails.
Apply the same transaction pattern to the multi-statement delete path and
normalizeSortOrder helper as well, using their existing method names to locate
the write sequences.

Source: Coding guidelines

@legeling legeling merged commit 6935647 into main Jul 9, 2026
4 of 7 checks passed
@legeling legeling deleted the agent/prompt-output-format branch July 9, 2026 11:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant